/*
 * This program is free software: you can redistribute it and/or modify it under
 * the terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <http://www.gnu.org/licenses/>.
 */
package net.sf.l2j.gameserver.model.entity;

import java.util.Calendar;
import java.util.logging.Level;
import java.util.logging.Logger;

import javolution.util.FastList;
import net.sf.l2j.Config;
import net.sf.l2j.gameserver.ThreadPoolManager;
import net.sf.l2j.gameserver.ai.CtrlIntention;
import net.sf.l2j.gameserver.instancemanager.DuelManager;
import net.sf.l2j.gameserver.model.L2Effect;
import net.sf.l2j.gameserver.model.actor.L2Character;
import net.sf.l2j.gameserver.model.actor.instance.L2PcInstance;
import net.sf.l2j.gameserver.network.SystemMessageId;
import net.sf.l2j.gameserver.network.serverpackets.ActionFailed;
import net.sf.l2j.gameserver.network.serverpackets.ExDuelEnd;
import net.sf.l2j.gameserver.network.serverpackets.ExDuelReady;
import net.sf.l2j.gameserver.network.serverpackets.ExDuelStart;
import net.sf.l2j.gameserver.network.serverpackets.ExDuelUpdateUserInfo;
import net.sf.l2j.gameserver.network.serverpackets.L2GameServerPacket;
import net.sf.l2j.gameserver.network.serverpackets.PlaySound;
import net.sf.l2j.gameserver.network.serverpackets.SocialAction;
import net.sf.l2j.gameserver.network.serverpackets.SystemMessage;
import net.sf.l2j.gameserver.skills.AbnormalEffect;

public class Duel
{
protected static final Logger _log = Logger.getLogger(Duel.class.getName());

public static final int DUELSTATE_NODUEL = 0;
public static final int DUELSTATE_DUELLING = 1;
public static final int DUELSTATE_DEAD = 2;
public static final int DUELSTATE_WINNER = 3;
public static final int DUELSTATE_INTERRUPTED = 4;

// =========================================================
// Data Field
private int _duelId;
private L2PcInstance _playerA;
private L2PcInstance _playerB;
private boolean _partyDuel;
private Calendar _duelEndTime;
private int _surrenderRequest = 0;
private int _countdown = 4;
private boolean _finished = false;

private FastList<PlayerCondition> _playerConditions;

public static enum DuelResultEnum
{
	Continue,
	Team1Win,
	Team2Win,
	Team1Surrender,
	Team2Surrender,
	Canceled,
	Timeout
}

// =========================================================
// Constructor
public Duel(L2PcInstance playerA, L2PcInstance playerB, int partyDuel, int duelId)
{
	_duelId = duelId;
	_playerA = playerA;
	_playerB = playerB;
	_partyDuel = partyDuel == 1 ? true : false;
	
	_duelEndTime = Calendar.getInstance();
	if (_partyDuel)
		_duelEndTime.add(Calendar.SECOND, 300);
	else
		_duelEndTime.add(Calendar.SECOND, 120);
	
	_playerConditions = new FastList<PlayerCondition>();
	
	setFinished(false);
	
	if (_partyDuel)
	{
		// increase countdown so that start task can teleport players
		_countdown++;
		// inform players that they will be portet shortly
		SystemMessage sm = new SystemMessage(SystemMessageId.IN_A_MOMENT_YOU_WILL_BE_TRANSPORTED_TO_THE_SITE_WHERE_THE_DUEL_WILL_TAKE_PLACE);
		broadcastToTeam1(sm);
		broadcastToTeam2(sm);
	}
	// Schedule duel start
	ThreadPoolManager.getInstance().scheduleGeneral(new ScheduleStartDuelTask(this), 3000);
}

// ===============================================================
// Nested Class

public class PlayerCondition
{
private L2PcInstance _player;
private double _hp;
private double _mp;
private double _cp;
private boolean _paDuel;
private int _x, _y, _z;
private FastList<L2Effect> _debuffs;

public PlayerCondition(L2PcInstance player, boolean partyDuel)
{
	if (player == null)
		return;
	_player = player;
	_hp = _player.getCurrentHp();
	_mp = _player.getCurrentMp();
	_cp = _player.getCurrentCp();
	_paDuel = partyDuel;
	
	if (_paDuel)
	{
		_x = _player.getX();
		_y = _player.getY();
		_z = _player.getZ();
	}
}

public void restoreCondition()
{
	if (_player == null)
		return;
	_player.setCurrentHp(_hp);
	_player.setCurrentMp(_mp);
	_player.setCurrentCp(_cp);
	
	if (_paDuel)
	{
		teleportBack();
	}
	if (_debuffs != null) // Debuff removal
	{
		for (L2Effect temp : _debuffs)
			if (temp != null)
				temp.exit();
	}
}

public void registerDebuff(L2Effect debuff)
{
	if (_debuffs == null)
		_debuffs = new FastList<L2Effect>();
	
	_debuffs.add(debuff);
}

public void teleportBack()
{
	if (_paDuel)
	{
		_player.setIsParalyzed(false);
		_player.stopAbnormalEffect(AbnormalEffect.HOLD_1);
		
		if (_player.isSitting())
			_player.standUp();
		
		_player.teleToLocation(_x, _y, _z);
	}
}

public L2PcInstance getPlayer()
{
	return _player;
}
}

// ===============================================================
// Schedule task
public class ScheduleDuelTask implements Runnable
{
private Duel _duel;

public ScheduleDuelTask(Duel duel)
{
	_duel = duel;
}

public void run()
{
	try
	{
		DuelResultEnum status = _duel.checkEndDuelCondition();
		
		if (status == DuelResultEnum.Canceled)
		{
			// do not schedule duel end if it was interrupted
			setFinished(true);
			_duel.endDuel(status);
		}
		else if (status != DuelResultEnum.Continue)
		{
			setFinished(true);
			playKneelAnimation();
			ThreadPoolManager.getInstance().scheduleGeneral(new ScheduleEndDuelTask(_duel, status), 5000);
		}
		else
			ThreadPoolManager.getInstance().scheduleGeneral(this, 1000);
	}
	catch (Exception e)
	{
		_log.log(Level.SEVERE, "", e);
	}
}
}

public class ScheduleStartDuelTask implements Runnable
{
private Duel _duel;

public ScheduleStartDuelTask(Duel duel)
{
	_duel = duel;
}

public void run()
{
	try
	{
		// start/continue countdown
		_duel._playerA.setIsInPreDuelState(true);
		_duel._playerB.setIsInPreDuelState(true);
		int count = _duel.countdown();
		
		if (count == 4)
		{
			// players need to be teleportet first
			//TODO: stadia manager needs a function to return an unused stadium for duels
			// currently only teleports to the same stadium
			_duel.teleportPlayers(21319, 252948, -2022, 17634, 253958, -2022);
			
			// give players 20 seconds to complete teleport and get ready (its ought to be 30 on offical..)
			ThreadPoolManager.getInstance().scheduleGeneral(this, 20000);
		}
		else if (count > 0) // duel not started yet - continue countdown
		{
			ThreadPoolManager.getInstance().scheduleGeneral(this, 1000);
		}
		else
			_duel.startDuel();
	}
	catch (Exception e)
	{
		_log.log(Level.SEVERE, "", e);
	}
}
}

public class ScheduleEndDuelTask implements Runnable
{
private Duel _duel;
private DuelResultEnum _result;

public ScheduleEndDuelTask(Duel duel, DuelResultEnum result)
{
	_duel = duel;
	_result = result;
}

public void run()
{
	try
	{
		_duel.endDuel(_result);
	}
	catch (Exception e)
	{
		_log.log(Level.SEVERE, "", e);
	}
}
}

// ========================================================
// Method - Private

/**
 * Stops all players from attacking.
 * Used for duel timeout / interrupt.
 *
 */
private void stopFighting()
{
	ActionFailed af = ActionFailed.STATIC_PACKET;
	if (_partyDuel)
	{
		for (L2PcInstance temp : _playerA.getParty().getPartyMembers())
		{
			temp.abortCast();
			temp.getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
			temp.setTarget(null);
			temp.sendPacket(af);
		}
		for (L2PcInstance temp : _playerB.getParty().getPartyMembers())
		{
			temp.abortCast();
			temp.getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
			temp.setTarget(null);
			temp.sendPacket(af);
		}
	}
	else
	{
		_playerA.abortCast();
		_playerB.abortCast();
		_playerA.getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
		_playerA.setTarget(null);
		_playerB.getAI().setIntention(CtrlIntention.AI_INTENTION_ACTIVE);
		_playerB.setTarget(null);
		_playerA.sendPacket(af);
		_playerB.sendPacket(af);
	}
}

// ========================================================
// Method - Public

/**
 * Check if a player engaged in pvp combat (only for 1on1 duels)
 * @return returns true if a duelist is engaged in Pvp combat
 */
public boolean isDuelistInPvp(boolean sendMessage)
{
	if (_partyDuel)
	{
		// Party duels take place in arenas - should be no other players there
		return false;
	}
	else if (_playerA.getPvpFlag() != 0 || _playerB.getPvpFlag() != 0)
	{
		if (sendMessage)
		{
			String engagedInPvP = "The duel was canceled because a duelist engaged in PvP combat.";
			_playerA.sendMessage(engagedInPvP);
			_playerB.sendMessage(engagedInPvP);
		}
		return true;
	}
	return false;
}

/**
 * Starts the duel
 *
 */
public void startDuel()
{
	

	// Save player Conditions
	savePlayerConditions();
	
	if (_playerA == null || _playerB == null || _playerA.isInDuel() || _playerB.isInDuel())
	{
		// clean up
		_playerConditions.clear();
		_playerConditions = null;
		DuelManager.getInstance().removeDuel(this);
		return;
	}
	
	if (_partyDuel)
	{
		// set isInDuel() state
		// cancel all active trades, just in case? xD
		for (L2PcInstance temp : _playerA.getParty().getPartyMembers())
		{
			temp.setIsInPreDuelState(false);
			temp.cancelActiveTrade();
			temp.setIsInDuel(_duelId);
			temp.setTeam(1);
			temp.broadcastUserInfo();
			broadcastToTeam2(new ExDuelUpdateUserInfo(temp));
		}
		for (L2PcInstance temp : _playerB.getParty().getPartyMembers())
		{
			temp.setIsInPreDuelState(false);
			temp.cancelActiveTrade();
			temp.setIsInDuel(_duelId);
			temp.setTeam(2);
			temp.broadcastUserInfo();
			broadcastToTeam1(new ExDuelUpdateUserInfo(temp));
		}
		
		// Send duel Start packets
		ExDuelReady ready = new ExDuelReady(1);
		ExDuelStart start = new ExDuelStart(1);
		
		broadcastToTeam1(ready);
		broadcastToTeam2(ready);
		broadcastToTeam1(start);
		broadcastToTeam2(start);
	}
	else
	{
		// set isInDuel() state

		
		_playerA.setIsInDuel(_duelId);
		_playerA.setTeam(1);
		_playerB.setIsInDuel(_duelId);
		_playerB.setTeam(2);
		
		// Send duel Start packets
		ExDuelReady ready = new ExDuelReady(0);
		ExDuelStart start = new ExDuelStart(0);
		
		broadcastToTeam1(ready);
		broadcastToTeam2(ready);
		broadcastToTeam1(start);
		broadcastToTeam2(start);
		
		broadcastToTeam1(new ExDuelUpdateUserInfo(_playerB));
		broadcastToTeam2(new ExDuelUpdateUserInfo(_playerA));
		
		_playerA.broadcastUserInfo();
		_playerB.broadcastUserInfo();
	}
	
	// play sound
	PlaySound ps = new PlaySound(1, "B04_S01", 0, 0, 0, 0, 0);
	broadcastToTeam1(ps);
	broadcastToTeam2(ps);
	
	// start duelling task
	ThreadPoolManager.getInstance().scheduleGeneral(new ScheduleDuelTask(this), 1000);
}

/**
 * Save the current player condition: hp, mp, cp, location
 *
 */
public void savePlayerConditions()
{
	if (_partyDuel)
	{
		for (L2PcInstance temp : _playerA.getParty().getPartyMembers())
		{
			_playerConditions.add(new PlayerCondition(temp, _partyDuel));
		}
		for (L2PcInstance temp : _playerB.getParty().getPartyMembers())
		{
			_playerConditions.add(new PlayerCondition(temp, _partyDuel));
		}
	}
	else
	{
		_playerConditions.add(new PlayerCondition(_playerA, _partyDuel));
		_playerConditions.add(new PlayerCondition(_playerB, _partyDuel));
	}
}

/**
 * Restore player conditions
 * @param was the duel canceled?
 */
public void restorePlayerConditions(boolean abnormalDuelEnd)
{
	// update isInDuel() state for all players
	if (_partyDuel)
	{
		for (L2PcInstance temp : _playerA.getParty().getPartyMembers())
		{
			temp.setIsInPreDuelState(false);
			temp.setIsInDuel(0);
			temp.setTeam(0);
			temp.broadcastUserInfo();
		}
		for (L2PcInstance temp : _playerB.getParty().getPartyMembers())
		{
			temp.setIsInPreDuelState(false);
			temp.setIsInDuel(0);
			temp.setTeam(0);
			temp.broadcastUserInfo();
		}
	}
	else
	{

		_playerA.setIsInPreDuelState(false);
		_playerB.setIsInPreDuelState(false);
		_playerA.setIsInDuel(0);
		_playerA.setTeam(0);
		_playerA.broadcastUserInfo();
		_playerB.setIsInDuel(0);
		_playerB.setTeam(0);
		_playerB.broadcastUserInfo();
	}
	
	// if it is an abnormal DuelEnd do not restore hp, mp, cp
	if (abnormalDuelEnd)
		return;
	
	// restore player conditions
	for (FastList.Node<PlayerCondition> e = _playerConditions.head(), end = _playerConditions.tail(); (e = e.getNext()) != end;)
	{
		e.getValue().restoreCondition();
	}
}

/**
 * Get the duel id
 * @return id
 */
public int getId()
{
	return _duelId;
}

/**
 * Returns the remaining time
 * @return remaining time
 */
public int getRemainingTime()
{
	return (int) (_duelEndTime.getTimeInMillis() - Calendar.getInstance().getTimeInMillis());
}

/**
 * Get the player that requestet the duel
 * @return duel requester
 */
public L2PcInstance getPlayerA()
{
	return _playerA;
}

/**
 * Get the player that was challenged
 * @return challenged player
 */
public L2PcInstance getPlayerB()
{
	return _playerB;
}

/**
 * Returns whether this is a party duel or not
 * @return is party duel
 */
public boolean isPartyDuel()
{
	return _partyDuel;
}

public void setFinished(boolean mode)
{
	_finished = mode;
}

public boolean getFinished()
{
	return _finished;
}

public void teleportPlayers(int x, int y, int z, int x2, int y2, int z2)
{
	if (!_partyDuel)
		return;
	
	for (L2PcInstance temp : _playerA.getParty().getPartyMembers())
	{
		if (temp == null)
			continue;
		temp.teleToLocation(x , y , z, true);
	}
	
	for (L2PcInstance temp : _playerB.getParty().getPartyMembers())
	{
		if (temp == null)
			continue;
		temp.teleToLocation(x2, y2, z2, true);
	}
}

/**
 * Broadcast a packet to the challanger team
 *
 */
public void broadcastToTeam1(L2GameServerPacket packet)
{
	if (_playerA == null)
		return;
	
	if (_partyDuel && _playerA.getParty() != null)
	{
		for (L2PcInstance temp : _playerA.getParty().getPartyMembers())
			temp.sendPacket(packet);
	}
	else
		_playerA.sendPacket(packet);
}

/**
 * Broadcast a packet to the challenged team
 *
 */
public void broadcastToTeam2(L2GameServerPacket packet)
{
	if (_playerB == null)
		return;
	
	if (_partyDuel && _playerB.getParty() != null)
	{
		for (L2PcInstance temp : _playerB.getParty().getPartyMembers())
			temp.sendPacket(packet);
	}
	else
		_playerB.sendPacket(packet);
}

/**
 * Get the duel winner
 * @return winner
 */
public L2PcInstance getWinner()
{
	if (!getFinished() || _playerA == null || _playerB == null)
		return null;
	if (_playerA.getDuelState() == DUELSTATE_WINNER)
		return _playerA;
	if (_playerB.getDuelState() == DUELSTATE_WINNER)
		return _playerB;
	return null;
}

/**
 * Get the duel looser
 * @return looser
 */
public L2PcInstance getLooser()
{
	if (!getFinished() || _playerA == null || _playerB == null)
		return null;
	if (_playerA.getDuelState() == DUELSTATE_WINNER)
		return _playerB;
	else if (_playerA.getDuelState() == DUELSTATE_WINNER)
		return _playerA;
	return null;
}

/**
 * Playback the bow animation for all loosers
 *
 */
public void playKneelAnimation()
{
	L2PcInstance looser = getLooser();
	
	if (looser == null)
		return;
	
	if (_partyDuel && looser.getParty() != null)
	{
		for (L2PcInstance temp : looser.getParty().getPartyMembers())
			temp.broadcastPacket(new SocialAction(temp.getObjectId(), 7));
	}
	else
		looser.broadcastPacket(new SocialAction(looser.getObjectId(), 7));
}

/**
 * Do the countdown and send message to players if necessary
 * @return current count
 */
public int countdown()
{
	_countdown--;
	
	if (_countdown > 3)
		return _countdown;
	
	// Broadcast countdown to duelists
	SystemMessage sm = null;
	if (_countdown > 0)
	{
		sm = new SystemMessage(SystemMessageId.THE_DUEL_WILL_BEGIN_IN_S1_SECONDS);
		sm.addNumber(_countdown);
	}
	else
		sm = new SystemMessage(SystemMessageId.LET_THE_DUEL_BEGIN);
	
	broadcastToTeam1(sm);
	broadcastToTeam2(sm);
	
	return _countdown;
}

/**
 * The duel has reached a state in which it can no longer continue
 * @param duel result
 */
@SuppressWarnings("incomplete-switch")
public void endDuel(DuelResultEnum result)
{
	if (_playerA == null || _playerB == null)
	{
		//clean up
		_playerConditions.clear();
		_playerConditions = null;
		DuelManager.getInstance().removeDuel(this);
		return;
	}
	
	// inform players of the result
	SystemMessage sm = null;
	switch (result)
	{
	case Team1Win:
		restorePlayerConditions(false);
		// send SystemMessage
		if (_partyDuel)
			sm = new SystemMessage(SystemMessageId.C1_PARTY_HAS_WON_THE_DUEL);
		else
			sm = new SystemMessage(SystemMessageId.C1_HAS_WON_THE_DUEL);
		sm.addString(_playerA.getDisplayName());
		
		broadcastToTeam1(sm);
		broadcastToTeam2(sm);
		
		if (Config.L2JMOD_ACHIEVEMENT_SYSTEM && !_playerA.getIP().equals(_playerB.getIP()))
		{
			_playerA.getCounters().duelsWon++;
		}
		break;
	case Team2Win:
		restorePlayerConditions(false);
		// send SystemMessage
		if (_partyDuel)
			sm = new SystemMessage(SystemMessageId.C1_PARTY_HAS_WON_THE_DUEL);
		else
			sm = new SystemMessage(SystemMessageId.C1_HAS_WON_THE_DUEL);
		sm.addString(_playerB.getDisplayName());
		
		broadcastToTeam1(sm);
		broadcastToTeam2(sm);
		
		if (Config.L2JMOD_ACHIEVEMENT_SYSTEM && !_playerB.getIP().equals(_playerA.getIP()))
		{
			_playerB.getCounters().duelsWon++;
		}
		break;
	case Team1Surrender:
		restorePlayerConditions(false);
		// send SystemMessage
		if (_partyDuel)
			sm = new SystemMessage(SystemMessageId.SINCE_C1_PARTY_WITHDREW_FROM_THE_DUEL_S2_PARTY_HAS_WON);
		else
			sm = new SystemMessage(SystemMessageId.SINCE_C1_WITHDREW_FROM_THE_DUEL_S2_HAS_WON);
		sm.addString(_playerA.getDisplayName());
		sm.addString(_playerB.getDisplayName());
		
		broadcastToTeam1(sm);
		broadcastToTeam2(sm);
		
		if (Config.L2JMOD_ACHIEVEMENT_SYSTEM && !_playerB.getIP().equals(_playerA.getIP()))
		{
			_playerB.getCounters().duelsWon++;
		}
		break;
	case Team2Surrender:
		restorePlayerConditions(false);
		// send SystemMessage
		if (_partyDuel)
			sm = new SystemMessage(SystemMessageId.SINCE_C1_PARTY_WITHDREW_FROM_THE_DUEL_S2_PARTY_HAS_WON);
		else
			sm = new SystemMessage(SystemMessageId.SINCE_C1_WITHDREW_FROM_THE_DUEL_S2_HAS_WON);
		sm.addString(_playerB.getDisplayName());
		sm.addString(_playerA.getDisplayName());
		
		broadcastToTeam1(sm);
		broadcastToTeam2(sm);
		
		if (Config.L2JMOD_ACHIEVEMENT_SYSTEM && !_playerA.getIP().equals(_playerB.getIP()))
		{
			_playerA.getCounters().duelsWon++;
		}
		break;
	case Canceled:
		stopFighting();
		// dont restore hp, mp, cp
		restorePlayerConditions(true);
		//TODO: is there no other message for a canceled duel?
		// send SystemMessage
		sm = new SystemMessage(SystemMessageId.THE_DUEL_HAS_ENDED_IN_A_TIE);
		
		broadcastToTeam1(sm);
		broadcastToTeam2(sm);
		break;
	case Timeout:
		stopFighting();
		// hp,mp,cp seem to be restored in a timeout too...
		restorePlayerConditions(false);
		// send SystemMessage
		sm = new SystemMessage(SystemMessageId.THE_DUEL_HAS_ENDED_IN_A_TIE);
		
		broadcastToTeam1(sm);
		broadcastToTeam2(sm);
		break;
	}
	
	// Send end duel packet
	ExDuelEnd duelEnd = null;
	if (_partyDuel)
		duelEnd = new ExDuelEnd(1);
	else
		duelEnd = new ExDuelEnd(0);
	
	broadcastToTeam1(duelEnd);
	broadcastToTeam2(duelEnd);
	
	//clean up
	_playerConditions.clear();
	_playerConditions = null;
	DuelManager.getInstance().removeDuel(this);
}

/**
 * Did a situation occur in which the duel has to be ended?
 * @return DuelResultEnum duel status
 */
public DuelResultEnum checkEndDuelCondition()
{
	// one of the players might leave during duel
	if (_playerA == null || _playerB == null)
		return DuelResultEnum.Canceled;
	
	// got a duel surrender request?
	if (_surrenderRequest != 0)
	{
		if (_surrenderRequest == 1)
			return DuelResultEnum.Team1Surrender;
		else
			return DuelResultEnum.Team2Surrender;
	}
	// duel timed out
	else if (getRemainingTime() <= 0)
	{
		return DuelResultEnum.Timeout;
	}
	// Has a player been declared winner yet?
	else if (_playerA.getDuelState() == DUELSTATE_WINNER)
	{
		// If there is a Winner already there should be no more fighting going on
		stopFighting();
		return DuelResultEnum.Team1Win;
	}
	else if (_playerB.getDuelState() == DUELSTATE_WINNER)
	{
		// If there is a Winner already there should be no more fighting going on
		stopFighting();
		return DuelResultEnum.Team2Win;
	}
	
	// More end duel conditions for 1on1 duels
	else if (!_partyDuel)
	{
		// Duel was interrupted e.g.: player was attacked by mobs / other players
		if (_playerA.getDuelState() == DUELSTATE_INTERRUPTED || _playerB.getDuelState() == DUELSTATE_INTERRUPTED)
			return DuelResultEnum.Canceled;
		
		// Are the players too far apart?
		if (!_playerA.isInsideRadius(_playerB, 2000, false, false))
			return DuelResultEnum.Canceled;
		
		// Did one of the players engage in PvP combat?
		if (isDuelistInPvp(true))
			return DuelResultEnum.Canceled;
		
		// is one of the players in a Siege, Peace or PvP zone?
		if (_playerA.isInsideZone(L2Character.ZONE_PEACE) || _playerB.isInsideZone(L2Character.ZONE_PEACE) || _playerA.isInsideZone(L2Character.ZONE_SIEGE) || _playerB.isInsideZone(L2Character.ZONE_SIEGE)
				|| _playerA.isInsideZone(L2Character.ZONE_PVP) || _playerB.isInsideZone(L2Character.ZONE_PVP))
			return DuelResultEnum.Canceled;
	}
	
	return DuelResultEnum.Continue;
}

/**
 * Register a surrender request
 * @param surrendering player
 */
public void doSurrender(L2PcInstance player)
{
	// already recived a surrender request
	if (_surrenderRequest != 0)
		return;
	
	// stop the fight
	stopFighting();
	
	// TODO: Can every party member cancel a party duel? or only the party leaders?
	if (_partyDuel)
	{
		if (_playerA.getParty().getPartyMembers().contains(player))
		{
			_surrenderRequest = 1;
			for (L2PcInstance temp : _playerA.getParty().getPartyMembers())
			{
				temp.setDuelState(DUELSTATE_DEAD);
			}
			for (L2PcInstance temp : _playerB.getParty().getPartyMembers())
			{
				temp.setDuelState(DUELSTATE_WINNER);
			}
		}
		else if (_playerB.getParty().getPartyMembers().contains(player))
		{
			_surrenderRequest = 2;
			for (L2PcInstance temp : _playerB.getParty().getPartyMembers())
			{
				temp.setDuelState(DUELSTATE_DEAD);
			}
			for (L2PcInstance temp : _playerA.getParty().getPartyMembers())
			{
				temp.setDuelState(DUELSTATE_WINNER);
			}
			
		}
	}
	else
	{
		if (player == _playerA)
		{
			_surrenderRequest = 1;
			_playerA.setDuelState(DUELSTATE_DEAD);
			_playerB.setDuelState(DUELSTATE_WINNER);
		}
		else if (player == _playerB)
		{
			_surrenderRequest = 2;
			_playerB.setDuelState(DUELSTATE_DEAD);
			_playerA.setDuelState(DUELSTATE_WINNER);
		}
	}
}

/**
 * This function is called whenever a player was defeated in a duel
 * @param dieing player
 */
public void onPlayerDefeat(L2PcInstance player)
{
	// Set player as defeated
	player.setDuelState(DUELSTATE_DEAD);
	
	if (_partyDuel)
	{
		boolean teamdefeated = true;
		for (L2PcInstance temp : player.getParty().getPartyMembers())
		{
			if (temp.getDuelState() == DUELSTATE_DUELLING)
			{
				teamdefeated = false;
				break;
			}
		}
		
		if (teamdefeated)
		{
			L2PcInstance winner = _playerA;
			if (_playerA.getParty().getPartyMembers().contains(player))
				winner = _playerB;
			
			for (L2PcInstance temp : winner.getParty().getPartyMembers())
			{
				temp.setDuelState(DUELSTATE_WINNER);
			}
		}
		else
		{
			player.stopMove(null, false);
			player.abortAttack();
			player.abortCast();
			
			if (!player.isSitting())
				player.sitDown();
			
			player.startAbnormalEffect(AbnormalEffect.HOLD_1);
			player.setIsParalyzed(true);
		}
	}
	else
	{
		if (player != _playerA && player != _playerB)
			_log.warning("Error in onPlayerDefeat(): player is not part of this 1vs1 duel");
		
		if (_playerA == player)
			_playerB.setDuelState(DUELSTATE_WINNER);
		else
			_playerA.setDuelState(DUELSTATE_WINNER);
	}
}

/**
 * This function is called whenever a player leaves a party
 * @param leaving player
 */
public void onRemoveFromParty(L2PcInstance player)
{
	// if it isnt a party duel ignore this
	if (!_partyDuel)
		return;
	
	// this player is leaving his party during party duel
	// if hes either playerA or playerB cancel the duel and port the players back
	if (player == _playerA || player == _playerB)
	{
		for (FastList.Node<PlayerCondition> e = _playerConditions.head(), end = _playerConditions.tail(); (e = e.getNext()) != end;)
		{
			e.getValue().teleportBack();
			e.getValue().getPlayer().setIsInDuel(0);
		}
		
		_playerA = null;
		_playerB = null;
	}
	else
		// teleport the player back & delete his PlayerCondition record
	{
		for (FastList.Node<PlayerCondition> e = _playerConditions.head(), end = _playerConditions.tail(); (e = e.getNext()) != end;)
		{
			if (e.getValue().getPlayer() == player)
			{
				e.getValue().teleportBack();
				_playerConditions.remove(e.getValue());
				break;
			}
		}
		player.setIsInDuel(0);
	}
}

public void onBuff(L2PcInstance player, L2Effect debuff)
{
	for (FastList.Node<PlayerCondition> e = _playerConditions.head(), end = _playerConditions.tail(); (e = e.getNext()) != end;)
	{
		if (e.getValue().getPlayer() == player)
		{
			e.getValue().registerDebuff(debuff);
			return;
		}
	}
}
}
